package com.iflytek.jzcpx.procuracy.card.service.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import cn.hutool.http.HttpUtil;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.iflytek.jzcpx.procuracy.card.common.CardConstants;
import com.iflytek.jzcpx.procuracy.card.common.FileUtils;
import com.iflytek.jzcpx.procuracy.card.common.enums.CardFileProcessEnum;
import com.iflytek.jzcpx.procuracy.card.common.enums.CardFileSourceEnum;
import com.iflytek.jzcpx.procuracy.card.common.enums.CardFileStatusEnum;
import com.iflytek.jzcpx.procuracy.card.common.enums.CardResultEnum;
import com.iflytek.jzcpx.procuracy.card.component.FillAcceptFormHelper;
import com.iflytek.jzcpx.procuracy.card.component.FillHandleFormHelper;
import com.iflytek.jzcpx.procuracy.card.config.WsmbbmConfig;
import com.iflytek.jzcpx.procuracy.card.entity.CardFile;
import com.iflytek.jzcpx.procuracy.card.entity.CardFormField;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleFileInHandleParam;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleFullParam;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleImgInAcceptParam;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleInFolderParam;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleOuterInHandleParam;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleOuterSyncInHandleParam;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleResultData;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleResultData.ExtractInfo;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleResultData.ExtractInfoVec;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleResultData.Suspect;
import com.iflytek.jzcpx.procuracy.card.pojo.ElleWordInAcceptParam;
import com.iflytek.jzcpx.procuracy.card.pojo.InFolderFile;
import com.iflytek.jzcpx.procuracy.card.service.CardFileService;
import com.iflytek.jzcpx.procuracy.card.service.CardFormFieldService;
import com.iflytek.jzcpx.procuracy.card.service.CardService;
import com.iflytek.jzcpx.procuracy.common.result.Result;
import com.iflytek.jzcpx.procuracy.common.util.JSONUtil;
import com.iflytek.jzcpx.procuracy.ocr.common.constant.Constants;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbjg;
import com.iflytek.jzcpx.procuracy.ocr.recognize.model.OcrResultRequestVo;
import com.iflytek.jzcpx.procuracy.ocr.service.OcrService;
import com.iflytek.jzcpx.procuracy.tools.csb.CsbUtil;
import com.iflytek.jzcpx.procuracy.tools.elle.ElleClient;
import com.iflytek.jzcpx.procuracy.tools.elle.ElleTextTypeEnum;
import com.iflytek.jzcpx.procuracy.tools.ocr.OcrClient;
import com.iflytek.jzcpx.procuracy.tools.ocr.OcrConstants;
import com.iflytek.jzcpx.procuracy.tools.ocr.OcrUtil;
import com.iflytek.jzcpx.procuracy.tools.roma.RomaUtil;
import com.iflytek.sxs.dfs.client.FdfsClient;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * @author <a href=mailto:ktyi@iflytek.com>伊开堂</a>
 * @date 2019/8/20 14:40
 */
@Service
public class CardServiceImpl implements CardService {
    public static final Logger logger = LoggerFactory.getLogger(CardServiceImpl.class);

    @Autowired
    private WsmbbmConfig wsmbbmConfig;

    @Autowired
    private CsbUtil csbUtil;
    @Autowired
    private RomaUtil romaUtil;
    @Value("${csb.enabled}")
    private boolean useCSB;
    @Value("${roma.enabled}")
    private boolean useHwApi;

    @Autowired
    private OcrClient ocrClient;
    @Autowired
    private ElleClient elleClient;
    @Autowired
    private FdfsClient fdfsClient;
    @Autowired
    private FillAcceptFormHelper fillAcceptFormHelper;
    @Autowired
    private FillHandleFormHelper fillHandleFormHelper;
    @Autowired
    private CardFileService cardFileService;
    @Autowired
    private CardFormFieldService cardFormFieldService;

    @Value("${swx.server.card.getAJXX:/ajmx-service/api/interface/hqAjhzrrjbxxlb}")
    private String pathGetAJXX;
    @Value("${swx.server.card.getAJWSWJ:/ws-service/api/caseDoc/getfileInputStream}")
    private String pathGetAJWSWJ;
    @Value("${swx.server.card.getAJWSJZWJ:/jcnj-service/api/jcnjYxAjjzwj/getWlwsxx}")
    private String pathGetAJWSJZWJ;
    @Value("${swx.server.aj.ip:http://143.176.22.167:8080}")
    private String ajIp;
    @Autowired
    private OcrService ocrService;


    @Override
    public Result<JSONObject> extractFromImgInAccept(ElleImgInAcceptParam param) {
        // 表单结构, 抽取出来的数据要回填到这里
        String bdjg = param.getBdjg();
        // 案件类别编码, 不同的类别编码, 要回填的表单名称不一样
        String ajlbbm = param.getAjlbbm();
        // 文书类型
        String wslx = param.getWslx();
        // 文件编码(文件的唯一标识)
        String wjbm = param.getWjbm();

        // 获取对应的 elle 引擎文书类型
        ElleTextTypeEnum elleTextTypeEnum = ElleTextTypeEnum.fromWslx(wslx);
        if (elleTextTypeEnum == null) {
            logger.warn("根据文书类型无法获取对应的 elle 引擎文书类型, wslx: {}", wslx);
            return Result.failed(CardResultEnum.ELLE_FILE_TYPE_CONVERT_FAILED);
        }

        // 新增任务记录
        Long cardFileId = saveCardFileInAccept(elleTextTypeEnum, wjbm);

        // 识别多张图片的文本内容
        String fileContent = parseTextOfImages(cardFileId, param);
        // ocr 识别结果中标点为英文标点, elle 引擎要求传递中文标点
        fileContent = StringUtils.replaceEachRepeatedly(fileContent, new String[]{",", "."}, new String[]{"，", "。"});

        Result<JSONObject> result= extractAndFillInAccept(cardFileId, fileContent, elleTextTypeEnum, ajlbbm, bdjg);
        result.setFileId(cardFileId);
        return result;
    }

    /**
     * 受理阶段: 字段抽取并回填案卡
     *
     * @param cardFileId       抽取文件 id
     * @param fileContent      文件文本内容
     * @param elleTextTypeEnum 文书类型
     * @param ajlbbm           案件类别编码
     * @param bdjg             表单结构
     *
     * @return 回填后的表单响应
     */
    private Result<JSONObject> extractAndFillInAccept(Long cardFileId, String fileContent,
            ElleTextTypeEnum elleTextTypeEnum, String ajlbbm, String bdjg) {
        // 文本内容提取结果判断
        if (StringUtils.isBlank(fileContent)) {
            logger.warn("文本内容抽取结果为空");
            cardFileService.abnormalEnd(cardFileId, "文本内容抽取结果为空");
            return Result.failed(CardResultEnum.FILE_CONTENT_IS_BLANK);
        }

        // 请求 elle 引擎
        Result<ElleResultData> elleResult = requestElle(cardFileId, elleTextTypeEnum, fileContent);
        if (!elleResult.isSuccess()) {
            return Result.from(elleResult);
        }

        // 表单填充
        JSONObject resultBdjg = fillAcceptFormHelper
                .fill(cardFileId, ajlbbm, elleTextTypeEnum, bdjg, elleResult.getData());

        // 更新案卡文件记录状态为完成
        cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);

        return Result.success(resultBdjg);
    }

    /** 识别多张图片的文本内容 */
    private String parseTextOfImages(Long cardFileId, ElleImgInAcceptParam param) {
        List<String> ocrEngineStorePaths = new ArrayList<>();
        List<byte[]> wdwjl = param.getWdwjl();
        StringBuilder sb = new StringBuilder();
        for (byte[] fileBytes : wdwjl) {
            Result<String> result;
            try {
                // 调用 ocr 引擎
                result = ocrClient.request(new String(Base64.encodeBase64(fileBytes)), OcrClient.RequestPriority.HIGH);
                logger.info("ocr 请求完成");
            }
            catch (Exception e) {
                logger.warn("ocr请求异常", e);
                continue;
            }

            // 识别成功, 提取文本内容, 上传引擎结果到 fastDFS
            if (result.isSuccess()) {
                String resultData = result.getData();
                sb.append(OcrUtil.getTextByType(resultData, OcrConstants.STR_TWO));
                logger.info("纯文本提取完成");

                // 上传识别结果到 fastDFS
                byte[] bytes = resultData.getBytes(StandardCharsets.UTF_8);
                try (InputStream is = new ByteArrayInputStream(bytes)) {
                    String storePath = fdfsClient.uploadFile(is, bytes.length, param.getWjlx());
                    ocrEngineStorePaths.add(storePath);
                    logger.info("ocr 结果已上传 fastDFS");
                }
                catch (Exception e) {
                    logger.warn("上传 ocr 识别结果到 fastDFS 异常. fileId: {}", param.getWjbm(), e);
                }
            }
            else {
                logger.info("ocr请求失败， error: {}", result.getError());
            }
        }

        // 更新任务状态, 保存 ocr 存储路径
        cardFileService.updateByStoreOcrResult(cardFileId, Joiner.on(Constants.CARDFILE_OCRPATH_SEPERATOR)
                                                                 .join(ocrEngineStorePaths));
        return sb.toString();
    }

    /**
     * 新建案卡文件记录
     *
     * @param elleTextTypeEnum 文书类型
     * @param wjbm             文件编码
     *
     * @return
     */
    private Long saveCardFileInAccept(ElleTextTypeEnum elleTextTypeEnum, String wjbm) {
        CardFile cardFile = buildBaseCardFile(elleTextTypeEnum, CardFileProcessEnum.ACCEPT, CardFileSourceEnum.INNER,
                CardFileStatusEnum.INIT);
        cardFile.setWjbm(wjbm);
        cardFileService.save(cardFile);
        Long cardFileId = cardFile.getId();
        logger.info("新增案卡提取文件记录, id: {}", cardFileId);
        return cardFileId;
    }

    /** 构造基础的案卡文件抽取记录对象 */
    private CardFile buildBaseCardFile(ElleTextTypeEnum elleTextTypeEnum, CardFileProcessEnum processEnum,
            CardFileSourceEnum sourceEnum, CardFileStatusEnum statusEnum) {
        CardFile cardFile = new CardFile();
        Date now = new Date();
        cardFile.setCreateTime(now);
        cardFile.setUpdateTime(now);
        cardFile.setProcess(processEnum.name());
        cardFile.setSource(sourceEnum.name());
        cardFile.setType(elleTextTypeEnum.name());
        cardFile.setStatus(statusEnum.name());
        return cardFile;
    }

    /**
     * 请求elle引擎
     *
     * @param cardFileId   案卡文件 id
     * @param textTypeEnum 文书类型
     * @param fileContent  文档文本内容
     *
     * @return
     */
    private Result<ElleResultData> requestElle(Long cardFileId, ElleTextTypeEnum textTypeEnum, String fileContent) {
        // 2019.10.12 elle 引擎抽取的文本中有空格时会导致抽取结果的定位信息不准确, 对方要求我们去除空格后再调用 elle
        fileContent = StringUtils.remove(fileContent, " ");

        // 请求要素抽取引擎
        Result<String> elleResult;
        try {
            // 请求引擎
            elleResult = elleClient.request(textTypeEnum, fileContent);
            logger.info("elle 请求完成");
            logger.info(JSON.toJSONString(elleResult));
        }
        catch (IOException e) {
            logger.warn("请求要素抽取引擎抛出异常。", e);
            return Result.failed(CardResultEnum.ELLE_REQUEST_EXCEPTION);
        }

        if (elleResult != null && !elleResult.isSuccess()) {
            logger.warn("请求 elle 引擎失败. error: {}", elleResult.getError());
            return Result.failed(CardResultEnum.ELLE_REQUEST_FAILED);
        }

        try {
            // json转bean
            ElleResultData elleResultData = JSONUtil.toBean(elleResult.getData(), ElleResultData.class);
            
            // 过滤空嫌疑人belonger
			List<ExtractInfoVec> listExtractInfoVec = elleResultData.getExtractInfoVec();
			List<ExtractInfoVec> listExtractInfoVecNew = new ArrayList<>();
			for (ExtractInfoVec extractInfoVec : listExtractInfoVec) {
				ExtractInfo extractInfo = extractInfoVec.getExtractInfo();
				List<Suspect> sucpects = extractInfo.getSuspect();
				if (CollectionUtils.isNotEmpty(sucpects)) {
					sucpects = sucpects.stream().filter(v -> null != v.getLabel() && null != v.getLabel().getSuspectName()&& StringUtils.isNotEmpty(v.getLabel().getSuspectName()[0].getMean())).collect(Collectors.toList());
					// 对嫌疑人顺序排序
					sucpects.sort((a, b) -> a.getLabel().getSuspectName()[0].getPos_begin() - b.getLabel().getSuspectName()[0].getPos_begin());
					// 过滤之后再塞回去
					extractInfo.setSuspect(sucpects);
					extractInfoVec.setExtractInfo(extractInfo);
					listExtractInfoVecNew.add(extractInfoVec);
				}
			}
			if (CollectionUtils.isNotEmpty(listExtractInfoVecNew)) {
				elleResultData.setExtractInfoVec(listExtractInfoVecNew);
			}

            // 更新任务状态并保存文件内容
            cardFileService.updateStatusAndFileContent(cardFileId, CardFileStatusEnum.REQUESTED_ELLE_ENGINE, fileContent);

            // 识别结果上传 fastDFS
            byte[] bytes = elleResult.getData().getBytes(StandardCharsets.UTF_8);
            try (InputStream is = new ByteArrayInputStream(bytes)) {
                String storePath = fdfsClient.uploadFile(is, bytes.length, CardConstants.FASTDFS_SUFFIX_TXT);
                cardFileService.updateByStoreElleResult(cardFileId, storePath);
                logger.info("上传 elle 识别结果到 fastDFS 完成");
            }
            catch (Exception e) {
                // 上传失败时不能阻断主流程, 故而捕获异常后仅打印日志
                logger.warn("上传 elle 识别结果到 fastDFS 异常. cardFileId: {}", cardFileId, e);
            }

            return Result.success(elleResultData);
        }
        catch (IOException e) {
            logger.info("要素抽取引擎结果转换异常。", e);
            return Result.failed(CardResultEnum.ELLE_RESULT_PARSE_FAILED);
        }
    }

    @Override
    public Result<JSONObject> extractFromWordInAccept(ElleWordInAcceptParam param) {
        // 表单结构, 抽取出来的数据要回填到这里
        String bdjg = param.getBdjg();
        // 案件类别编码, 不同的类别编码, 要回填的表单名称不一样
        String ajlbbm = param.getAjlbbm();
        // 文书类型
        String wslx = param.getWslx();
        // 文件编码(文件的唯一标识)
        String wjbm = param.getWjbm();

        // 获取文书类型对应的 elle 引擎文书类型
        ElleTextTypeEnum elleTextTypeEnum = ElleTextTypeEnum.fromWslx(wslx);
        if (elleTextTypeEnum == null) {
            logger.warn("根据文书类型无法获取对应的 elle 引擎文书类型, wslx: {}", wslx);
            return Result.failed(CardResultEnum.ELLE_FILE_TYPE_CONVERT_FAILED);
        }

        // 创建案卡提取记录
        Long cardFileId = saveCardFileInAccept(elleTextTypeEnum, wjbm);
        //云南现场支持单层pdf抽取
        String wordText=StringUtils.EMPTY;
        String wjlx=param.getWjlx();
        if("pdf".equalsIgnoreCase(wjlx)) {
        	OcrResultRequestVo ocrParam=new OcrResultRequestVo();
        	ocrParam.setWjbh(UUID.randomUUID().toString());
        	ocrParam.setWjhz(".pdf");
        	ocrParam.setWjnr(Base64.encodeBase64String(param.getWdwjl()));
        	ocrParam.setSfwjnr(false);
        	ocrParam.setSfwjwb(true);
        	ocrParam.setSfwjxx(false);
        	ocrParam.setSfjp(true);
        	ocrParam.setSfxz(true);
        	ocrParam.setWjxh(UUID.randomUUID().toString());
        	Wjsbjg wjsbjg=ocrService.recognize(ocrParam);
        	wordText=wjsbjg.getTxtnr();
        }else {
        	// 提取word文本内容
            Result<String> extrctResult = extractTextFromWord(cardFileId, param.getWdwjl());
            if (!extrctResult.isSuccess()) {
                return Result.from(extrctResult);
            }
            wordText = extrctResult.getData();
        }
        Result<JSONObject> result= extractAndFillInAccept(cardFileId, wordText, elleTextTypeEnum, ajlbbm, bdjg);
        result.setFileId(cardFileId);
        return result;
    }

    /** 从word文件流中抽取文本内容 */
    private Result<String> extractTextFromWord(Long cardFileId, byte[] wdwjl) {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(wdwjl)) {
            return Result.success(FileUtils.extractWordText(inputStream));
        }
        catch (Exception e) {
            logger.warn("提取word文本内容异常", e);
            cardFileService.abnormalEnd(cardFileId, "提取word文本内容异常");
            return Result.failed("提取word文本内容异常");
        }
    }

    @Override
    public Result<JSONObject> extractFileInHandle(ElleFileInHandleParam elleFileInHandleParam) {
        String bmsah = elleFileInHandleParam.getBmsah();
        String wsslbh = elleFileInHandleParam.getWsslbh();
        String wsmbbm = elleFileInHandleParam.getWsmbbm();
        String wsmc = elleFileInHandleParam.getWsmc();
        String ajlbbm = elleFileInHandleParam.getAjlbbm();
        JSONObject bdjg = elleFileInHandleParam.getBdjg();
        byte[] wswj = elleFileInHandleParam.getWswj();
        String wjkzm=elleFileInHandleParam.getWjkzm();

        // 获取文书模板对应的elle引擎文件类型
        ElleTextTypeEnum elleTextTypeEnum = wsmbbmConfig.getElleTextType(wsmbbm);
        if (elleTextTypeEnum == null) {
            logger.warn("根据文书模板编码无法获取对应的 elle 引擎文书类型, wsmbbm: {}", wsmbbm);
            return Result.failed(CardResultEnum.ELLE_FILE_TYPE_CONVERT_FAILED);
        }

        // 创建案卡提取记录
        CardFile cardFile = saveCardFile(bmsah, wsslbh, wsmc, wsmbbm, elleTextTypeEnum);

        return extractAndFillInHandle(cardFile, bmsah, ajlbbm, elleTextTypeEnum, wswj, bdjg,wjkzm);
    }

    /**
     * 办理阶段: 字段抽取+保存+回填
     *
     * @param cardFile         抽取文件 id
     * @param bmsah            部门受案号
     * @param ajlbbm           案件类别编码
     * @param elleTextTypeEnum elle文书类型
     * @param wjl              二进制文件内容
     * @param bdjg             表单结构, 抽取出的字段回填到这里
     * @param wjkzm            文件扩展名
     *
     * @return
     */
    private Result<JSONObject> extractAndFillInHandle(CardFile cardFile, String bmsah, String ajlbbm,
            ElleTextTypeEnum elleTextTypeEnum, byte[] wjl, JSONObject bdjg,String wjkzm) {
        Long cardFileId = cardFile.getId();
        String wordText=StringUtils.EMPTY;
        if("pdf".equalsIgnoreCase(wjkzm)) {
        	OcrResultRequestVo ocrParam=new OcrResultRequestVo();
        	ocrParam.setWjbh(UUID.randomUUID().toString());
        	ocrParam.setWjhz(".pdf");
        	ocrParam.setWjnr(Base64.encodeBase64String(wjl));
        	ocrParam.setSfwjnr(false);
        	ocrParam.setSfwjwb(true);
        	ocrParam.setSfwjxx(false);
        	ocrParam.setSfjp(true);
        	ocrParam.setSfxz(true);
        	ocrParam.setWjxh(UUID.randomUUID().toString());
        	Wjsbjg wjsbjg=ocrService.recognize(ocrParam);
        	wordText=wjsbjg.getTxtnr();
        }else {
        	// 提取word文本内容
            Result<String> extrctResult = extractTextFromWord(cardFileId, wjl);
            if (!extrctResult.isSuccess() || StringUtils.isBlank(extrctResult.getData())) {
                logger.warn("读取文档文本内容失败");
                cardFileService.abnormalEnd(cardFileId, "读取文档文本内容失败");
                return Result.from(extrctResult);
            }
            wordText = extrctResult.getData();
        }

        // 调用elle引擎
        Result<ElleResultData> elleResult = requestElle(cardFileId, elleTextTypeEnum, wordText);
        if (!elleResult.isSuccess()) {
            logger.warn("调用elle引擎失败");
            cardFileService.abnormalEnd(cardFileId, "调用elle引擎失败");
            return Result.from(elleResult);
        }

        // 抽取字段并保存
        List<CardFormField> fields = fillHandleFormHelper
                .extractField(cardFileId, ajlbbm, elleTextTypeEnum, elleResult.getData());
        if (CollectionUtils.isEmpty(fields)) {
            logger.info("未抽取到任何字段, bmsah: {}, cardFileId: {}", bmsah, cardFileId);
            cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);
            // 案卡表单原样返回
            return Result.success(bdjg);
        }
        cardFormFieldService.saveBatch(fields);
        logger.info("批量保存抽取的字段数据, cardFieldId: {}, size: {}", cardFileId, fields.size());

        // 同步类接口(单一文书编写/外来文书(同步))才需要回填表单, 异步接口只需要将抽取的字段保存即可
        JSONObject resultBdjg = null;
        if (bdjg != null) {
            // 构造抽取字段与所属文档的对应关系
            Map<CardFormField, CardFile> fieldAndFileMap = new HashMap<>();
            for (CardFormField field : fields) {
                fieldAndFileMap.put(field, cardFile);
            }

            // 填充表单数据（只填充数据项为空的数据）
            resultBdjg = fillHandleFormHelper.fill(ajlbbm, bdjg, fieldAndFileMap);
        }

        cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);

        return Result.success(resultBdjg);
    }

    /**
     * 保存案卡文件，保存之前先删除已有的记录
     *
     * @param bmsah            部门受案号
     * @param wsslbh           文书实例编号
     * @param wsmc             文书名称
     * @param wsmbbh           文书模板编号
     * @param elleTextTypeEnum 对应的elle引擎文本类型
     *
     * @return
     */
    private CardFile saveCardFile(String bmsah, String wsslbh, String wsmc, String wsmbbh, ElleTextTypeEnum elleTextTypeEnum) {
        // 如果文件提取记录已存在则需要先删除
        List<CardFile> existFiles = cardFileService.findByWsslbh(bmsah, wsslbh);
        if (CollectionUtils.isNotEmpty(existFiles)) {
            List<Long> fileIds = existFiles.stream().map(CardFile::getId).collect(Collectors.toList());
            cardFileService.removeByIds(fileIds);
            cardFormFieldService.removeByCardFileIds(fileIds);
        }

        CardFile cardFile = buildBaseCardFile(elleTextTypeEnum, CardFileProcessEnum.HANDLE, CardFileSourceEnum.INNER, CardFileStatusEnum.INIT);
        cardFile.setBmsah(bmsah);
        cardFile.setWsslbh(wsslbh);
        cardFile.setWsmc(wsmc);
        cardFile.setWsmbbm(wsmbbh);
        cardFileService.save(cardFile);
        logger.info("新增案卡提取文件记录, id: {}", cardFile.getId());
        return cardFile;
    }

    @Override
    public Result  inFolder(ElleInFolderParam elleInFolderParam) {
        String bmsah = elleInFolderParam.getBmsah();

        // 获取案件类别编码
        String ajlbbm = requestAjlbbm(bmsah);
        if (StringUtils.isBlank(ajlbbm)) {
            logger.warn("无法获取案件类别编码, bmsah: {}", bmsah);
            return Result.failed(CardResultEnum.CSB_REQUEST_FAILED);
        }

        //获取要抽取的文件，word文件，循环抽取
        List<InFolderFile> fileList = elleInFolderParam.getWslb();
        for (InFolderFile file : fileList) {
            logger.info("处理入卷文件, file: {}",  file);
            // 文书实例编号
            String wsslbh = file.getWsslbh();
            // 文书模板编号
            String wsmbbh = file.getWsmbbh();
            // 文书名称
            String wsmc = file.getWsmc();
            // 获取文书模板对应的elle引擎文件类型
            ElleTextTypeEnum elleTextTypeEnum = wsmbbmConfig.getElleTextType(wsmbbh);
            if (elleTextTypeEnum == null) {
                logger.warn("根据文书模板编号无法获取对应的 elle 引擎文书类型, wsslbh: {}, wslx: {}", wsslbh, wsmbbh);
                continue;
            }

            // 获取案件文书文件（接口17.8）
            byte[] fileData = getAJWSWJ(wsslbh);
            if (ArrayUtils.isEmpty(fileData)) {
                return null;
            }

            // 创建案卡提取记录
            CardFile cardFile = saveCardFile(bmsah, wsslbh, wsmc, wsmbbh, elleTextTypeEnum);

            extractAndFillInHandle(cardFile, bmsah, ajlbbm, elleTextTypeEnum, fileData, null,"doc");

            // extractAndSaveFormField(bmsah, wsslbh, wsmc, wsmbbh, elleTextTypeEnum, ajlbbm);
        }

        return Result.success();
    }

    /**
     * 抽取字段并保存
     *
     * @param bmsah            部门受案号
     * @param wsslbh           文书实例编号
     * @param wsmc             文书名称
     * @param wsmbbh           文书模板编号
     * @param elleTextTypeEnum 文书对应的elle文本类型
     * @param ajlbbm           赛威讯案件类别编码
     *
     * @return
     */
    private List<CardFormField> extractAndSaveFormField(String bmsah, String wsslbh, String wsmc, String wsmbbh, ElleTextTypeEnum elleTextTypeEnum,
            String ajlbbm) {
        // 获取案件文书文件（接口17.8）
        byte[] fileData = getAJWSWJ(wsslbh);
        if (ArrayUtils.isEmpty(fileData)) {
            return null;
        }

        // 创建案卡提取记录
        CardFile cardFile = saveCardFile(bmsah, wsslbh, wsmc, wsmbbh, elleTextTypeEnum);
        Long cardFileId = cardFile.getId();

        // 读取文档文本
        Result<String> extrctResult = extractTextFromWord(cardFileId, fileData);
        if (!extrctResult.isSuccess() || StringUtils.isBlank(extrctResult.getData())) {
            cardFileService.abnormalEnd(cardFileId, "读取到的文档文本内容失败");
            return null;
        }
        String wordText = extrctResult.getData();

        // 调用elle引擎
        Result<ElleResultData> elleResult = requestElle(cardFileId, elleTextTypeEnum, wordText);
        if (!elleResult.isSuccess()) {
            cardFileService.abnormalEnd(cardFileId, elleResult.getError().getMsg());
            return null;
        }

        // 保存字段
        List<CardFormField> fields = fillHandleFormHelper
                .extractField(cardFileId, ajlbbm, elleTextTypeEnum, elleResult.getData());
        cardFormFieldService.saveBatch(fields);
        logger.info("批量保存抽取的字段数据, cardFieldId: {}, size: {}", cardFileId, fields.size());

        cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);

        return fields;
    }

    /**
     * 获取案件文书文件
     *
     * @param wsslbh     文书实例编号
     *
     * @return 文件字节数组
     */
    private byte[] getAJWSWJ(String wsslbh) {
        Map<String, Object> params = new HashMap<>();
        params.put("wsslbh", wsslbh);

        if (useCSB) {
            return csbUtil.getAJWSWJ(JSONUtil.toStrDefault(params));
        }else if(useHwApi) {
        	return romaUtil.getAJWSWJ(JSONUtil.toStrDefault(params));
        }

        // 直连赛威讯研发环境
        try {
            String url = ajIp + pathGetAJWSWJ;
            String resp = HttpUtil.post(url, JSONUtil.toStrDefault(params), 30000);
            if (StringUtils.isNotBlank(resp)) {
                JSONObject respJSON = JSONObject.parseObject(resp);
                byte[] data = respJSON.getBytes("data");
                if (respJSON.getBooleanValue("success")) {
                    logger.info("成功获取到案件文书文件卷宗，wsslbh:{}, fileSize:{}", wsslbh, data.length);
                    return data;
                }

                logger.warn("获取案件文书文件返回失败响应");
            }
            else {
                logger.warn("获取案件文书文件响应为空");
            }
        }
        catch (Exception e) {
            logger.warn("获取案件文书文件异常", e);
        }
        return null;
    }

    @Override
    public Result<JSONObject> handleOuterSync(ElleOuterSyncInHandleParam elleOuterSyncInHandleParam) {
        String bmsah = elleOuterSyncInHandleParam.getBmsah();
        String jzwjh = elleOuterSyncInHandleParam.getJzwjh();
        String jzwjlx = elleOuterSyncInHandleParam.getJzwjlx();
        String jzwjmc = elleOuterSyncInHandleParam.getJzwjmc();
        String ajlbbm = elleOuterSyncInHandleParam.getAjlbbm();
        byte[] jzwswj = elleOuterSyncInHandleParam.getJzwswj();
        JSONObject bdjg = elleOuterSyncInHandleParam.getBdjg();

        if (!FileUtils.isWordFile(jzwjlx)) {
            logger.info("不支持该外来文书类型：{}, bmsah: {}, jzwjlx: {}", jzwjlx, bmsah, jzwjlx);
            return Result.failed(CardResultEnum.FILE_TYPE_NOT_SUPPORT);
        }

        // 外来文书统一按判决书处理
        ElleTextTypeEnum elleTextTypeEnum = ElleTextTypeEnum.pjs;

        // 创建案卡提取记录
        CardFile cardFile = saveCardFileByOuterFile(bmsah, jzwjh, jzwjmc, elleTextTypeEnum);

        Result<JSONObject> result= extractAndFillInHandle(cardFile, bmsah, ajlbbm, elleTextTypeEnum, jzwswj, bdjg,"doc");
        result.setFileId(cardFile.getId());
        return result;

        /*Long cardFileId = cardFile.getId();
        logger.info("新增案卡提取文件记录, id: {}", cardFileId);

        // 提取word文本内容
        Result<String> extrctResult = extractTextFromWord(cardFileId, elleOuterSyncInHandleParam.getJzwswj());
        if (!extrctResult.isSuccess()) {
            return Result.from(extrctResult);
        }
        String wordText = extrctResult.getData();

        // 调用elle引擎
        Result<ElleResultData> elleResult = requestElle(cardFileId, elleTextTypeEnum, wordText);
        if (!elleResult.isSuccess()) {
            return Result.from(elleResult);
        }

        // 抽取字段
        List<CardFormField> fields = fillHandleFormHelper
                .extractField(cardFileId, ajlbbm, elleTextTypeEnum, elleResult.getData());
        if (CollectionUtils.isEmpty(fields)) {
            logger.info("未抽取到任何字段, bmsah: {}, jzwjh: {}", bmsah, jzwjh);
            cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);
            // 案卡表单原样返回
            return Result.success(elleOuterSyncInHandleParam.getBdjg());
        }

        // 保存字段
        cardFormFieldService.saveBatch(fields);
        logger.info("批量保存抽取的字段数据, cardFieldId: {}, size: {}", cardFileId, fields.size());

        // 构造抽取字段与所属文档的对应关系
        Map<CardFormField, CardFile> fieldAndFileMap = new HashMap<>();
        for (CardFormField field : fields) {
            fieldAndFileMap.put(field, cardFile);
        }

        // 填充表单数据（只填充数据项为空的数据）
        JSONObject resultBdjg = fillHandleFormHelper.fill(ajlbbm, elleOuterSyncInHandleParam.getBdjg(), fieldAndFileMap);

        cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);

        return Result.success(resultBdjg);*/
    }

    /**
     * 基于外来文书创建案卡提取记录
     *
     * @param bmsah            部门受案号
     * @param jzwjh            卷宗文件号
     * @param jzwjmc           卷宗文件名称
     * @param elleTextTypeEnum 文书类型
     *
     * @return
     */
    private CardFile saveCardFileByOuterFile(String bmsah, String jzwjh, String jzwjmc,
            ElleTextTypeEnum elleTextTypeEnum) {
        // 如果文件提取记录已存在则需要先删除
        List<CardFile> existFiles = cardFileService.findByJzwjh(bmsah, jzwjh);
        if (CollectionUtils.isNotEmpty(existFiles)) {
            List<Long> fileIds = existFiles.stream().map(CardFile::getId).collect(Collectors.toList());
            cardFileService.removeByIds(fileIds);
            cardFormFieldService.removeByCardFileIds(fileIds);
        }

        CardFile cardFile = buildBaseCardFile(elleTextTypeEnum, CardFileProcessEnum.HANDLE, CardFileSourceEnum.OUTER,
                CardFileStatusEnum.INIT);
        cardFile.setBmsah(bmsah);
        cardFile.setJzwjh(jzwjh);
        cardFile.setWsmc(jzwjmc);
        cardFileService.save(cardFile);
        return cardFile;
    }

    @Override
    public Result handleOuterAsync(ElleOuterInHandleParam elleOuterInHandleParam) {
        String bmsah = elleOuterInHandleParam.getBmsah();
        String jzwjh = elleOuterInHandleParam.getJzwjh();

        // 外来文书统一按判决书处理
        ElleTextTypeEnum elleTextTypeEnum = ElleTextTypeEnum.pjs;

        //获取要抽取的文件
        Result<JSONObject> wjResult = getAJWSJZWJ(bmsah, jzwjh);
        if (!wjResult.isSuccess()) {
            return Result.from(wjResult);
        }

        // 文件可能有多种格式，目前只考虑word文件，进行elle抽取
        JSONObject jzFile = wjResult.getData();
        // 文件扩展名
        String wjkzm = jzFile.getString("wjkzm");
        // 文件名称
        String wjmc = jzFile.getString("wjmc");
        // 文件流
        byte[] wjl = jzFile.getBytes("wjl");
        if (!FileUtils.isWordFile(wjkzm)) {
            logger.info("不支持外来文书类型：{}, bmsah: {}, jzwjh: {}", wjkzm, bmsah, jzwjh);
            return Result.failed(CardResultEnum.FILE_TYPE_NOT_SUPPORT);
        }

        // 获取案件类别编码
        String ajlbbm = requestAjlbbm(bmsah);
        if (StringUtils.isBlank(ajlbbm)) {
            logger.warn("无法获取案件类别编码, bmsah: {}", bmsah);
            return Result.failed(CardResultEnum.CSB_REQUEST_FAILED);
        }

        // 创建案卡提取记录
        CardFile cardFile = saveCardFileByOuterFile(bmsah, jzwjh, wjmc, elleTextTypeEnum);

        Result<JSONObject> result= extractAndFillInHandle(cardFile, bmsah, ajlbbm, elleTextTypeEnum, wjl, null,"doc");
        result.setFileId(cardFile.getId());
        return result;
        /*Long cardFileId = cardFile.getId();
        logger.info("新增案卡提取文件记录, id: {}", cardFileId);

        // 读取文档文本
        Result<String> extrctResult = extractTextFromWord(cardFileId, wjl);
        if (!extrctResult.isSuccess()) {
            logger.warn("读取文档文本内容失败");
            cardFileService.abnormalEnd(cardFileId, "读取文档文本内容失败");
            return Result.from(extrctResult);
        }

        // 调用elle引擎
        String wordText = extrctResult.getData();
        Result<ElleResultData> elleResult = requestElle(cardFileId, elleTextTypeEnum, wordText);
        if (!elleResult.isSuccess()) {
            logger.warn("调用elle引擎失败");
            cardFileService.abnormalEnd(cardFileId, "调用elle引擎失败");
            return Result.from(elleResult);
        }

        // 保存字段
        List<CardFormField> fields = fillHandleFormHelper
                .extractField(cardFileId, ajlbbm, elleTextTypeEnum, elleResult.getData());
        if (CollectionUtils.isEmpty(fields)) {
            logger.info("未抽取到任何字段, bmsah: {}, jzwjh: {}", bmsah, jzwjh);
            cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);
            // 案卡表单原样返回
            return Result.success();
        }
        cardFormFieldService.saveBatch(fields);
        logger.info("批量保存抽取的字段数据, cardFieldId: {}, size: {}", cardFileId, fields.size());

        cardFileService.updateStatus(cardFileId, CardFileStatusEnum.END);

        return Result.success();*/
    }

    /**
     * 根据部门受案号获取案件类别编码
     *
     * @param bmsah 部门受案号
     *
     * @return 案件类别编码, 可能为 null
     */
    private String requestAjlbbm(String bmsah) {
        Map<String, Object> params = new HashMap<>();
        params.put("bmsah", Lists.newArrayList(bmsah));

        JSONObject ajxx = null;
        if (useCSB) {
            ajxx = csbUtil.getAJXX(JSONUtil.toStrDefault(params));
        }else if(useHwApi) {
        	ajxx= romaUtil.getAJXX(JSONUtil.toStrDefault(params));
        }
        else{
            // 直连赛威讯研发环境
            try {
                String url =  ajIp + pathGetAJXX;
                String resp = HttpUtil.post(url, JSONUtil.toStrDefault(params), 30000);
                if (StringUtils.isNotBlank(resp)) {
                    JSONObject respJson = JSONObject.parseObject(resp);
                    if (respJson.getBooleanValue("success")) {
                        logger.info("成功根据bmsah获取案件信息, resp: {}", respJson.toJSONString());
                        ajxx = (JSONObject) respJson.getJSONArray("data").get(0);
                    }
                }
            }
            catch (Exception e) {
                logger.warn("根据bmsah获取案件信息异常.", e);
            }
        }

        if (ajxx == null) {
            logger.warn("根据bmsah获取案件信息失败, bmsah: {}",bmsah);
            return null;
        }

        return ajxx.getString("ajlbbm");
    }

    /**
     * 获取案件文书卷宗文件(接口 9)
     *
     * @param bmsah      部门受案号
     * @param jzwjh      卷宗文件号
     *
     * @return 文件流
     */
    private Result<JSONObject> getAJWSJZWJ(String bmsah, String jzwjh) {
        Map<String, Object> params = new HashMap<>();
        params.put("bmsah", bmsah);
        params.put("jzwjh", jzwjh);

        if (useCSB) {
            JSONObject data = csbUtil.getAJWSJZWJ(JSONUtil.toStrDefault(params));
            if (data == null) {
                return Result.failed(CardResultEnum.CSB_REQUEST_FAILED);
            }
            return Result.success(data);
        }else if(useHwApi) {
        	JSONObject data = romaUtil.getAJWSJZWJ(JSONUtil.toStrDefault(params));
            if (data == null) {
                return Result.failed(CardResultEnum.CSB_REQUEST_FAILED);
            }
            return Result.success(data);
        }

        // 直连赛威讯研发环境
        try {
            String url =  ajIp + pathGetAJWSJZWJ;
            String resp = HttpUtil.post(url, JSONUtil.toStrDefault(params), 30000);
            if (StringUtils.isNotBlank(resp)) {
                JSONObject respJson = JSONObject.parseObject(resp);
                if (respJson.getBooleanValue("success")) {
                    return Result.success(respJson.getJSONObject("data"));
                }
            }

            logger.warn("获取案件文书卷宗文件响应内容为空");
            return Result.failed(CardResultEnum.DOWNLOAD_FILE_STREAM_FAILED);
        }
        catch (Exception e) {
            logger.warn("获取案件文书卷宗文件异常.", e);
            return Result.failed(CardResultEnum.DOWNLOAD_FILE_STREAM_EXCEPTION);
        }
    }

    @Override
    public Result<JSONObject> handleFull(ElleFullParam elleFullParam) {
        String bmsah = elleFullParam.getBmsah();
        // 流程中获取案件在办文书信息并抽取步骤放弃执行，因为：文书多耗时，结构不完整，其他厂商都没做

        // 填充表单数据
        Map<CardFile, List<CardFormField>> fileFields = findInHandleFieldsByBmsah(bmsah);
        JSONObject bdjg = fillHandleFormHelper.fullFill(bmsah, elleFullParam.getBdjg(), elleFullParam.getAjlbbm(), fileFields);

        return Result.success(bdjg);
    }

    /** 根据部门受案号获取办理阶段抽取的字段 */
    private Map<CardFile, List<CardFormField>> findInHandleFieldsByBmsah(String bmsah) {
        List<CardFile> files = cardFileService.findInHandleFiles(bmsah);
        List<Long> fileIds = files.stream().map(CardFile::getId).collect(Collectors.toList());
        Map<Long, List<CardFormField>> fieldsByFileId = cardFormFieldService.findFieldsByFileId(fileIds);

        Map<CardFile, List<CardFormField>> fileFields = new HashMap<>();
        for (CardFile file : files) {
            fileFields.put(file, fieldsByFileId.get(file.getId()));
        }
        return fileFields;
    }
}
